
#include "config.h"

#ifdef ESP32
#include <WiFi.h>
#include <WebServer.h>
#endif

#include "ArduinoJson.h"
#include <WiFiUdp.h>
#include <SPI.h>
#include <EEPROM.h>

#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#endif

#ifdef WIFI_MANAGER

#include <WiFiManager.h>

WiFiManager g_wm;

#endif

// 液晶屏显示
#include <Wire.h>
#include "SSD1306Wire.h"
#include <ESPDateTime.h>

// 时间标语
#include "image.h"
#include "tomato.h"

// 常量定义  ----------------------------------------

// 屏幕 IIC 设备编号
#define IIC_DEVICE_ID 0x3c

// 对应的 GPIO 端口号
#define SDA 4
#define SCL 5

// EEPROM参数存储地址位
#define COUNTDOWN_TIME_ADDR 1
#define SCREEN_ON_ADDR 20
#define SCREEN_OFF_ADDR 22
#define WIFI_ADDR 30 //被写入数据的EEPROM地址编号  20wifi-ssid-psw

// 番茄工作法分钟时间
#define TOMATO_WORK_MINUTE 25

// 变量定义  -------------------------------------

SSD1306Wire g_display(IIC_DEVICE_ID, SDA, SCL);

// WIFI 配置参数
struct wifi_config
{
    char stassid[32]; //定义配网WIFI名长度(最大32字节)
    char stapsw[64];  //定义配网WIFI密码长度(最大64字节)
};

// wifi 登录密码
wifi_config g_wificonf = {{""}, {""}};

// 番茄工作法参数
struct _tomato_config
{
    // 结束时间
    time_t _tomato_end;

    // 番茄计数
    uint8_t _tomato_count;
};

// 番茄工作配置
_tomato_config g_tomato_config = {0, 0};

// 倒计时日期
char g_count_down_day[14];

// 屏幕打开时间
uint8_t g_screen_on = 6;

// 屏幕关闭时间
uint8_t g_screen_off = 23;

// 倒计时时间戳
time_t g_count_time;

// 网络更新时间戳
time_t g_update_time = 0;

// Web网站服务器
ESP8266WebServer g_web_server(80);

// 函数声明  -------------------------------------

uint8_t str2uint8(const char* val);
uint32_t str2uint32(const char* val);
void save_wifi_config();
void read_wifi_config();
void delete_wifi_config();
void show_timer_config();
void save_timer_config();
void read_timer_config();

#if WEBSEVER_EN
void web_server_init();
void web_handle();
#endif

void save_param_callback();
bool isShowTime();

// 代码开始  -------------------------------------------
/**
 * wifi ssid，psw保存到eeprom
 */ 
void save_wifi_config()
{
    //开始写入
    uint8_t *p = (uint8_t *)(&g_wificonf);
    for (unsigned int i = 0; i < sizeof(g_wificonf); i++)
    {
        EEPROM.write(i + WIFI_ADDR, *(p + i)); //在闪存内模拟写入
    }
    delay(10);
    EEPROM.commit(); //执行写入ROM
    Serial.println("Save wifi config ok!");
    delay(10);
}

/**
 * 保存时钟设置参数
 */ 
void save_timer_config()
{
    uint8_t *p = (uint8_t *)(&g_count_down_day);
    for (unsigned int i = 0; i < sizeof(g_count_down_day); i++)
    {
        EEPROM.write(i + COUNTDOWN_TIME_ADDR, *(p + i));
    }

    p = (uint8_t*)(&g_screen_on);
    for (unsigned int i = 0; i < sizeof(g_screen_on); i++)
    {
        EEPROM.write(i + SCREEN_ON_ADDR, *(p + i));
    }
    p = (uint8_t*)(&g_screen_off);
    for (unsigned int i = 0; i < sizeof(g_screen_off); i++)
    {
        EEPROM.write(i + SCREEN_OFF_ADDR, *(p + i));
    }
    delay(50);
    EEPROM.commit();
    Serial.println("Save timer config ok!");
    delay(50);
}

/**
 * 删除原有eeprom中的信息
 */ 
void delete_wifi_config()
{
    wifi_config deletewifi = {{""}, {""}};
    uint8_t *p = (uint8_t *)(&deletewifi);
    for (unsigned int i = 0; i < sizeof(deletewifi); i++)
    {
        EEPROM.write(i + WIFI_ADDR, *(p + i)); //在闪存内模拟写入
    }
    delay(10);
    EEPROM.commit(); //执行写入ROM
    delay(10);
}

/*
 * 从eeprom读取WiFi信息ssid，psw
 */
void read_wifi_config()
{
    uint8_t *p = (uint8_t *)(&g_wificonf);
    for (unsigned int i = 0; i < sizeof(g_wificonf); i++)
    {
        *(p + i) = EEPROM.read(i + WIFI_ADDR);
    }
    Serial.printf("Read WiFi Config.....\r\n");
    Serial.printf("SSID:%s\r\n", g_wificonf.stassid);
    Serial.printf("PSW:%s\r\n", g_wificonf.stapsw);
    Serial.printf("Connecting.....\r\n");
}

/**
 * 显示时钟配置
 */
void show_timer_config()
{
    Serial.printf("Read timer config.....\r\n");
    Serial.printf("Count time: %s\r\n", g_count_down_day);
    Serial.printf("Screen Time: %d %d\r\n", g_screen_on, g_screen_off);
}

/**
 * 读取时钟设置
 */ 
void read_timer_config()
{
    uint8_t *p = (uint8_t *)(&g_count_down_day);
    for (unsigned int i = 0; i < sizeof(g_count_down_day); i++)
    {
        *(p + i) = EEPROM.read(i + COUNTDOWN_TIME_ADDR);
    }

    p = (uint8_t *)(&g_screen_on);
    for (unsigned int i = 0; i < sizeof(g_screen_on); i++)
    {
        *(p + i) = EEPROM.read(i + SCREEN_ON_ADDR);
    }

    p = (uint8_t *)(&g_screen_off);
    for (unsigned int i = 0; i < sizeof(g_screen_off); i++)
    {
        *(p + i) = EEPROM.read(i + SCREEN_OFF_ADDR);
    }
    if (g_screen_on > 12) {
        g_screen_on = 12;
    }

    if (g_screen_off < 18) {
        g_screen_off = 18;
    }

    show_timer_config();
}

/**
 * 启动番茄工作法
 */ 
void start_tomato_count(int tcount)
{
    if (tcount <= 0) {
        return ;
    }
    time_t tnow = DateTime.getTime();
    tnow += (60 * TOMATO_WORK_MINUTE * tcount);
    g_tomato_config._tomato_end = tnow;
    g_tomato_config._tomato_count = (uint8_t)(tcount);    
}

/**
 * 判断是否番茄计数
 */ 
bool isTomato()
{
    if (g_tomato_config._tomato_count > 0) {
        return true;
    }
    return false;
}

/**
 * 计算番茄倒计时时间
 * @return
 */ 
String getTomatoCountTime()
{
    time_t now = DateTime.getTime();
    now        = g_tomato_config._tomato_end - now;
    if (now < 0) {
        // 倒计时完成
        g_tomato_config._tomato_count = 0;
        return String("00:00");
    }

    char tString[8];
    int minute = (int)(now / 60);
    int sec = (int)(now % 60);
    memset(tString, 0, sizeof(tString));
    sprintf(tString, "%02d:%02d", minute, sec);
    return String(tString);
}

/**
 * 日期转换测试
 * @param [in] val 转换日期
 * @return
 */ 
time_t make_count_time(const char* val)
{

    time_t tstamp = time(NULL);
    struct tm tinfo;
    char dbuf[5];
    const char* p = val;
    uint8_t ccount = 0;
    
    Serial.printf("parse val: %s\n", val);

    memset(dbuf, 0, sizeof(dbuf));
    while(*(p) != '-') {
        dbuf[ccount] = *p;
        p++;
        ccount++;
        if (ccount >= sizeof(dbuf))
        {
            break;
        }
    }
    tinfo.tm_year = str2uint32(dbuf);
    tinfo.tm_year -= 1900;
    p++;
    memset(dbuf, 0, sizeof(dbuf));
    ccount = 0;
    while(*(p) != '-') {
        dbuf[ccount] = *p;
        p++;
        ccount++;
        if (ccount >= sizeof(dbuf)) {
            break;
        }
    }
    tinfo.tm_mon = str2uint32(dbuf);
    if (tinfo.tm_mon > 0)
    {
        tinfo.tm_mon -= 1;
    }
    
    p++;
    memset(dbuf, 0, sizeof(dbuf));
    ccount = 0;
    while(*(p) > 0) {
        dbuf[ccount] = *p;
        p++;
        ccount++;
        if (ccount >= sizeof(dbuf)) {
            break;
        }
    }
    tinfo.tm_mday = str2uint32(dbuf);

    tinfo.tm_hour = 0;
    tinfo.tm_min = 0;
    tinfo.tm_sec = 0;

    tstamp = mktime(&tinfo);
    return tstamp;
}

// web网站相关函数
#if WEBSEVER_EN

/**
 * web设置页面
 */ 
void web_handle_root()
{
    String msg = "倒计时钟设置";

    char dbuf[16];
    int web_screen_on = 6;
    int web_screen_off = 23;
    int tomato_time = 1;
    int device_option = 0;
    if (g_web_server.hasArg("screen_on") || g_web_server.hasArg("screen_close") ||
        g_web_server.hasArg("count_down_time") || g_web_server.hasArg("web_set_rotation") ||
        g_web_server.hasArg("tomato_time"))
    {
        web_screen_on  = g_web_server.arg("screen_on").toInt();
        web_screen_off = g_web_server.arg("screen_off").toInt();
        memset(dbuf, 0, sizeof(dbuf));
        strcpy(dbuf, g_web_server.arg("count_down_time").c_str());
        tomato_time = g_web_server.arg("tomato_time").toInt();

        // 番茄工作法， 最大 24， 最小 1
        if (tomato_time > 24) {
            tomato_time = 24;
        }

        if (tomato_time <= 0) {
            tomato_time = 1;
        }
        
        device_option = g_web_server.arg("web_set_rotation").toInt();
        
        Serial.println("\n");
        Serial.printf("Count Down time: %s\n", g_count_down_day);        
        Serial.printf("web config: %d %d\n", web_screen_on, web_screen_off);

        delay(50);

        if (device_option == 0)
        {
            if (strcmp(dbuf, g_count_down_day) != 0 ||
            g_screen_on != (uint8)web_screen_on || g_screen_off != (uint8)web_screen_off)
            {
                strcpy(g_count_down_day, dbuf);
                g_screen_on = (uint8_t)web_screen_on;
                g_screen_off = (uint8_t)web_screen_off;
                save_timer_config();

                // 重新计算倒计时时间
                g_count_time = make_count_time(g_count_down_day);            
            }
            
        }

        if (device_option == 1)
        {
            // DONE: 执行番茄工作法
            start_tomato_count(tomato_time);
        }
        
        if (device_option >= 2)
        {
            Serial.println("设置设备操作参数!");
            if (device_option == 3) 
            {
                // DONE: 执行清除参数
                Serial.println("Reset Wifi Config!");
                delete_wifi_config();
            }
            // DONE: 执行设备重启
            Serial.println("Device Restart!");
            delay(2000);
            ESP.restart();
        }
    }

    //网页界面代码段
    //

    String content = "<html><head><meta charset='utf-8'></head><style>html,body{ background: #135f91; color: #fff; font-size: 10px;}</style>";
    content += "<body><form action='/' method='POST'><br><div>Count Down Timer Config</div><br>";
    content += "屏幕开(06 ~ 12):(default:06)<br><input type='text' name='screen_on' placeholder='06'><br>";
    content += "<br>屏幕关(18 ~ 24):(default:23)<br><input type='text' name='screen_off' placeholder='23'><br>";
    content += "<br>倒计时日期:(default:2025-01-01)<br><input type='text' name='count_down_time' placeholder='2025-01-01'><br>";
    content += "<br>番茄工作法:(default:1)<br><input type='text' name='tomato_time' placeholder='1'><br>";
    
    content += "<br>设备操作:<br>\
                    <input type='radio' name='web_set_rotation' value='0' checked> 保存参数<br> \
                    <input type='radio' name='web_set_rotation' value='1'> 启动番茄<br>\
                    <input type='radio' name='web_set_rotation' value='2'> 设备重启<br>\
                    <input type='radio' name='web_set_rotation' value='3'> 设备重置<br>";
    content += "<br><div><input type='submit' name='Save' value='Save'></form></div>" + msg + "<br>";
    content += "By x_hd2007@163.com<br>";
    content += "</body></html>";
    g_web_server.send(200, "text/html", content);

}

/**
 * 处理 url 未找到
 */ 
void web_handle_not_found()
{
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += g_web_server.uri();
    message += "\nMethod: ";
    message += (g_web_server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += g_web_server.args();
    message += "\n";
    for (uint8_t i = 0; i < g_web_server.args(); i++)
    {
        message += " " + g_web_server.argName(i) + ": " + g_web_server.arg(i) + "\n";
    }
    g_web_server.send(404, "text/plain", message);
}

/**
 * Web服务初始化
 */ 
void web_server_init()
{
    uint32_t counttime = 0; //记录创建mDNS的时间
    Serial.println("mDNS responder building...");
    counttime = millis();
    while (!MDNS.begin("SD3"))
    {
        if (millis() - counttime > 30000)
            ESP.restart(); //判断超过30秒钟就重启设备
    }

    Serial.println("mDNS responder started");

    //输出连接wifi后的IP地址

    g_web_server.on("/", web_handle_root);
    g_web_server.onNotFound(web_handle_not_found);

    //开启TCP服务
    g_web_server.begin();
    Serial.println("HTTP服务器已开启");

    Serial.print("本地IP： ");
    Serial.println(WiFi.localIP());
    //将服务器添加到mDNS
    MDNS.addService("http", "tcp", 80);
}

/**
 * Web网页设置函数
 */ 
void web_handle()
{
    MDNS.update();
    g_web_server.handleClient();
}

#endif // WEBSERVER_EN

#if WIFI_MANAGER > 0

/**
 * WEB配网 LCD 显示函数
 * 设备配置显示界面
 */
void wm_config_win()
{
    Serial.println("WiFi Manager Config Start!");
    g_display.clear();
    g_display.setTextAlignment(TEXT_ALIGN_LEFT);
    g_display.setFont(ArialMT_Plain_10);
    g_display.drawString(0, 0, "ap: nodemcuv3");
    g_display.drawString(0, 18, "visit: 192.168.4.1:80");
    g_display.drawString(0, 36, "to config the device!");
    g_display.display();
}

/**
 * 显示设备 WEB 站点信息
 * @return
 */ 
void show_web_server_info()
{
    g_display.setTextAlignment(TEXT_ALIGN_LEFT);
    g_display.setFont(ArialMT_Plain_16);
    g_display.drawString(0, 0, "Connect To:");

    g_display.setTextAlignment(TEXT_ALIGN_CENTER);
    g_display.drawString(64, 20, WiFi.localIP().toString());

    g_display.setTextAlignment(TEXT_ALIGN_LEFT);
    g_display.drawString(0, 40, "To Config Timer!");
}

/**
 * 显示倒计时时间
 */ 
void show_now_time_wnd()
{
    String nowStr = DateTime.format("%Y-%m-%d");
    g_display.setTextAlignment(TEXT_ALIGN_LEFT);
    g_display.setFont(ArialMT_Plain_10);
    g_display.drawString(0, 4, nowStr.c_str());

    nowStr = DateTime.format("%H:%M:%S");
    g_display.setTextAlignment(TEXT_ALIGN_RIGHT);
    g_display.setFont(ArialMT_Plain_16);
    g_display.drawString(128, 0, nowStr.c_str());

    if (isTomato())
    {
        // 绘制番茄图形
        g_display.drawXbm(0, 17, tomato_icon_width, tomato_icon_height, tomato_icon_bits);

        // 绘制番茄倒计时
        g_display.setTextAlignment(TEXT_ALIGN_LEFT);
        g_display.setFont(ArialMT_Plain_24);
        g_display.drawString(48, 21, getTomatoCountTime());
    }
    else
    {
    
        // 绘制图形标语
        g_display.drawXbm(0, 17, time_title_width, time_title_height, time_title_bits);

        String cdtime = "-->>";
        cdtime += g_count_down_day;
    
        g_display.setTextAlignment(TEXT_ALIGN_LEFT);
        g_display.setFont(ArialMT_Plain_10);
        g_display.drawString(4, 42,  cdtime.c_str());
    
    }

    time_t ntime = DateTime.getTime();
    ntime = g_count_time - ntime;
    ntime *= 1000;
    ntime += (1000 - (millis() % 1000));
    g_display.setTextAlignment(TEXT_ALIGN_CENTER);
    g_display.setFont(ArialMT_Plain_10);
    g_display.drawString(64, 54, String(ntime));
}

/**
 * 显示屏幕信息
 */ 
void show_screen_wnd()
{
    if ((DateTime.getTime() - g_update_time) <= 15 &&
        millis() <= 60000)
    {
        show_web_server_info();
    }
    else if (isShowTime())
    {
        show_now_time_wnd();
    }
}

/**
 * WEB配网函数
 * WiFi manager 启动配置
 */ 
void wm_config()
{
    WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP

    delay(3000);
    g_wm.resetSettings(); // wipe settings

    WiFiManagerParameter cd_time("cd_time", "cd_time", "2022-01-31", 10);
    WiFiManagerParameter screen_start("screen_open", "screen_open", "06", 2);
    WiFiManagerParameter screen_close("screen_close", "screen_close", "23", 2); 
    
    g_wm.addParameter(&cd_time);
    g_wm.addParameter(&screen_start);
    g_wm.addParameter(&screen_close);

    g_wm.setSaveConfigCallback(save_param_callback);

    // wifi scan settings
    g_wm.setMinimumSignalQuality(30); // set min RSSI (percentage) to show in scans, null = 8%

    bool res;
    res = g_wm.autoConnect("nodemcuv3"); // anonymous ap
    if (res) {
        Serial.println("wifi manager is autoconn ok!");
    }
    else {
        Serial.println("wifi manager autoconnect failed!");
    }

    while (!res)
    {
        ;
    }
}

/**
 * wifi manager 参数保存回调函数
 */ 
void save_param_callback()
{
    int pcount = g_wm.getParametersCount();
    WiFiManagerParameter** params = g_wm.getParameters();

    if (pcount <= 0) {
        return ;
    }

    if (params == NULL) {
        return ;
    }
    
    for (int pid = 0; pid < pcount; pid++)
    {
        WiFiManagerParameter* param = params[pid];
        if (param == NULL)
        {
            continue;
        }
            
        if (param->getID() != NULL && param->getValue() != NULL)
        {
            Serial.printf("%s: %s\n", param->getID(), param->getValue());
                
            if (strcmp(param->getID(), "cd_time") == 0)
            {
                memset(g_count_down_day, 0, sizeof(g_count_down_day));
                memcpy(g_count_down_day, param->getValue(), strlen(param->getValue()));
            }
            
            if (strcmp(param->getID(), "screen_open") == 0)
            {
                g_screen_on = str2uint8(param->getValue());
            }

            if (strcmp(param->getID(), "screen_close") == 0)
            {
                g_screen_off = str2uint8(param->getValue());
            }
        }
    }
        
    wm_config_win();
}

#endif

/**
 * 检查日期设置是否正确
 */ 
bool validateCountTime()
{
    bool ret = false;
    if (g_count_down_day[0] == '2' &&
        g_count_down_day[1] == '0' &&
        g_count_down_day[2] == '2')
    {
        ret = true;
    }
    return ret;
}

// 图形显示初始化
void setup_display()
{
    // 显示相关初始化
    Serial.println("display init");
    g_display.init();
    g_display.flipScreenVertically();
    g_display.setFont(ArialMT_Plain_10);
}

/**
 * 从网络更新时间
 */
void setup_datetime()
{
    DateTime.setTimeZone(8);
    DateTime.setServer("time1.aliyun.com");
    DateTime.begin(5000);
    if (!DateTime.isTimeValid())
    {
        DateTime.setTime(1574870400 + 7200);
        Serial.println("Failed to get time from ntp server!");
    }

    // 记录网络更新时间
    g_update_time = DateTime.getTime();
}

/**
 * 定时从网络更新时间
 */ 
void net_sync_datetime()
{
    int hour = DateTime.getParts().getHours();
    
    if ((DateTime.getTime() - g_update_time) > 1880)
    {
        Serial.println("resync time from network!");
        DateTime.begin(3000);
        g_update_time = DateTime.getTime();
    }
}

/**
 * 判断是否屏幕显示内容
 */ 
bool isShowTime()
{
    bool ret = false;
    int hour = DateTime.getParts().getHours();
    if (hour >= g_screen_on && hour < g_screen_off) {
        ret = true;
    }
    return ret;
}

/**
 * 加载设备设置参数
 */ 
void load_device_config()
{
    // 倒计时时钟设置读取
    read_timer_config();
    if (!validateCountTime())
    {
        strcpy(g_count_down_day, "2022-06-30");
    }
    g_count_time = make_count_time(g_count_down_day);
    
    // 扣除时区时差
    g_count_time -= (3600 * LOCAL_TIME_ZONE);

    // wifi 配置,链接并启动 WEB SERVER 
    read_wifi_config(); //读取存储的wifi信息
    Serial.print("正在连接WIFI ");
    Serial.println(g_wificonf.stassid);
}

/**
 * 启动 WIFI 
 */ 
void setup_wifi()
{
    WiFi.begin(g_wificonf.stassid, g_wificonf.stapsw);
    bool need_save = true;
    byte wcount = 0;

    // 等待 WIFI 链接
    while (wcount <= 10) {
        if (WiFi.status() != WL_CONNECTED)
        {
            Serial.print(".");
        }
        else {
            Serial.println("\nConnect Wifi Ok!");
            need_save = false;
            break;
        }
        delay(1000);
        wcount++;
    }

    // 链接不成功, 启动 WIFI MANAGER 
    while (WiFi.status() != WL_CONNECTED)
    {
    //使能web配网后自动将smartconfig配网失效
#ifdef WIFI_MANAGER
        wm_config_win();
        wm_config();
#endif
        break;
    }

    delay(10);

    // 链接成功 
    if (WiFi.status() == WL_CONNECTED)
    {
        strcpy(g_wificonf.stassid, WiFi.SSID().c_str()); //名称复制
        strcpy(g_wificonf.stapsw, WiFi.psk().c_str());   //密码复制

        // 保存 WIFI 参数
        if (need_save)
        {
            Serial.println("Save wifi config to eeprom");
            save_wifi_config();
            read_wifi_config();

            save_timer_config();
        }

#if WEBSEVER_EN
        //开启web服务器初始化
        web_server_init();
        delay(3000);
        Serial.println("web 服务器启动!");
#endif
    }

}

/**
 * arduino 安装函数入口
 */
void setup()
{
    Serial.begin(115200);
    Serial.println();

    // 显示固件版本信息
    Serial.printf("Device Version: %s\n", DEVICE_FIRMWARE_VERSION);

    // 显示初始化
    setup_display();

    // EEPROM 
    EEPROM.begin(1024);

    // 加载设备设置
    load_device_config();

    // 启动 WIFI 
    setup_wifi();

    // 更新时间
    setup_datetime();
    
}

/**
 * 程序主循环
 */ 
void loop()
{
#if WEBSEVER_EN
    web_handle();
#endif

    g_display.clear();
    show_screen_wnd();
    g_display.display();

    net_sync_datetime();
    delay(50);
}

/**
 * 字符串转数字
 * @param [in] val 字符串
 * @return 数字
 */ 
uint8_t str2uint8(const char* val)
{
    uint8_t ret = 0;
    if (val == NULL)
    {
        return ret;
    }

    uint8_t count = 0;
    const char* p = val;
    
    while(*(p) > 0)
    {
        ret *= 10;
        ret += (*p) - '0';
        count++;
        p++;
        if (count > 3)
        {
            break;
        }
    }
    return ret;    
}

/**
 * 字符转数字
 */ 
uint32_t str2uint32(const char* val)
{
    uint32_t ret = 0;
    if (val == NULL) {
        return ret;
    }

    uint8_t count = 0;
    const char* p = val;
    while(*(p) > 0)
    {
        ret *= 10;
        ret += (*p) - '0';
        count++;
        p++;
        if (count >= 4) {
            break;
        }
    }
    return ret;
}

